using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using DirectShowLib;

using OurMediaPlayer.Logic;
using OurMediaPlayer.Model;

namespace OurMediaPlayer
{
    public partial class MainForm : Form, ISampleGrabberCB
    {
        public MediaManager mediaManager = new MediaManager();
        public FiltersManager filtersManager = new FiltersManager();

        public Point filterDown = new Point(50, 50);
        public Point filterUp = new Point(100, 100);

        public int filterMinX;
        public int filterMinY;
        public int filterMaxX;
        public int filterMaxY;

        public enum VideoState
        {
            FastBackwards,
            Playing,
            Paused,
            Stopped,
            FastForwarding
        }

        public enum AudioState
        {
            Enabled,
            Disabled
        }

        public enum EditingState
        {
            SelectingFilter,
            None
        }

        public VideoState currentVideoState = VideoState.Stopped;
        public AudioState currentAudioState = AudioState.Enabled;
        public EditingState currentEditingState = EditingState.None;
        public int currentVolume = -100;

        public MainForm()
        {
            InitializeComponent();

            this.playlistForm = new PlaylistForm(this, 0.1f);
            this.filtersForm = new FiltersForm(this, 0.1f);
            this.tracksForm = new TracksForm(this, 0.1f);
        }

        public void CloseVideo()
        {
            this.guiTimer.Stop();
            this.mediaManager.CloseCurrentVideo();
            this.currentVideoState = VideoState.Stopped;
        }

        public void OpenVideo()
        {
            this.mediaManager.currentMedia.StopPlay += new MediaCoreEvent(videoCompleted);

            this.mediaManager.currentMedia.setVolume(this.currentVolume);

            double fps = this.mediaManager.currentMedia.getFramePerSecond();
            double duration = this.mediaManager.currentMedia.getDuration();

            this.guiTimer.Stop();
            this.guiTimer.Interval = (int)(1000 / (Math.Max(fps, 30)));
            this.guiTimer.Start();

            this.sliderBar.Maximum = (int)(fps * duration);
        }

        public void PlayVideo()
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            this.mediaTimer.Stop();

            if (this.currentAudioState == AudioState.Enabled)
                this.mediaManager.currentMedia.setVolume(this.currentVolume);

            // If we were stopped, start
            if (this.currentVideoState == VideoState.Stopped)
            {
                this.playPauseButton.BackgroundImage = global::OurMediaPlayer.Properties.Resources.pause;
                this.mediaManager.currentMedia.Start();
                //this.mediaManager.currentMedia.setVolume(this.currentVolume);
                this.currentVideoState = VideoState.Playing;
            }

            // If we're playing
            else if (this.currentVideoState == VideoState.Playing)
            {
                this.playPauseButton.BackgroundImage = global::OurMediaPlayer.Properties.Resources.play;
                this.mediaManager.currentMedia.Pause();
                this.currentVideoState = VideoState.Paused;
            }

            // Else
            else
            {
                this.playPauseButton.BackgroundImage = global::OurMediaPlayer.Properties.Resources.pause;
                this.mediaManager.currentMedia.Start();
                this.currentVideoState = VideoState.Playing;
            }
        }

        public void StopVideo()
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            // Stops the timer
            this.mediaTimer.Stop();

            this.playPauseButton.BackgroundImage = global::OurMediaPlayer.Properties.Resources.play;
            this.mediaManager.currentMedia.Stop();
            this.mediaManager.currentMedia.Rewind();
            this.currentVideoState = VideoState.Stopped;
        }

        private void fileButton_Click(object sender, EventArgs e)
        {
            // Opens the openFileDialog
            this.openFileDialog.ShowDialog();

            // If user canceled or choose no file
            if (this.openFileDialog.FileName == "")
                return;

            // Close old file
            if (this.mediaManager.currentMedia != null)
            {
                // File name changed, close the old file
                this.mediaManager.currentMedia.Stop();
                this.mediaManager.currentMedia.Dispose();
                this.mediaManager.currentMedia = null;
                this.currentVideoState = VideoState.Stopped;
                this.playPauseButton.ImageIndex = 0;
            }

            // Open new file
            try
            {
                // Open the file, provide a handle to play it in
                this.mediaManager.currentMedia = new Media(this.openFileDialog.FileName);
                this.currentVideoState = VideoState.Stopped;
                this.mediaManager.currentMedia.SetupVideoWindow(this.moviePanel);
            }
            catch (COMException ce)
            {
                MessageBox.Show("Failed to open file: " + ce.Message, "Open Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            this.mediaManager.currentMedia.StopPlay += new MediaCoreEvent(videoCompleted);

            double fps = this.mediaManager.currentMedia.getFramePerSecond();
            double duration = this.mediaManager.currentMedia.getDuration();

            this.guiTimer.Stop();
            this.guiTimer.Interval = (int)(1000 / (Math.Max(fps, 30)));
            this.guiTimer.Start();

            this.sliderBar.Maximum = (int)(fps * duration);
        }

        private void playPauseButton_Click(object sender, EventArgs e)
        {
            this.PlayVideo();
        }

        private void stopButton_Click(object sender, EventArgs e)
        {
            this.StopVideo();
        }

        private void ffButton_Click(object sender, EventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            this.mediaManager.currentMedia.End();
        }

        private void rewindButton_Click(object sender, EventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            this.mediaManager.currentMedia.Rewind();
        }

        private void fbButton_Click(object sender, EventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            this.mediaTimer.Interval = 1000 / ((int)this.mediaManager.currentMedia.getFramePerSecond() * 2);
            this.mediaTimer.Start();

            this.currentVideoState = VideoState.FastBackwards;
        }

        private void fastForwardButton_Click(object sender, EventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            this.mediaTimer.Interval = 1000 / ((int)this.mediaManager.currentMedia.getFramePerSecond() * 2);
            this.mediaTimer.Start();

            this.currentVideoState = VideoState.FastForwarding;
        }

        private void mediaTimerTick(object sender, EventArgs e)
        {
            if (currentVideoState == VideoState.FastBackwards)
                this.mediaManager.currentMedia.FastBackwards();

            if (currentVideoState == VideoState.FastForwarding)
                this.mediaManager.currentMedia.FastForward();
        }

        private void guiTimerTick(object sender, EventArgs e)
        {
            if (this.mediaManager.currentMedia != null)
            {
                double fps = this.mediaManager.currentMedia.getFramePerSecond();
                double position = this.mediaManager.currentMedia.getPosition();

                this.sliderBar.Value = (int)(position * fps);

                this.statusLabel.Text = Utils.SecondsToHHMMSS(this.mediaManager.currentMedia.getPosition()) + " / " + 
                                        Utils.SecondsToHHMMSS(this.mediaManager.currentMedia.getDuration());
            }
        }

        private void guiUpdate()
        {

        }

        public void mute() 
        {
            this.currentAudioState = AudioState.Disabled;

            if (this.mediaManager.currentMedia != null)
                this.mediaManager.currentMedia.setVolume(-10000);
        }

        public void unmute()
        {
            this.currentAudioState = AudioState.Enabled;

            if (this.mediaManager.currentMedia != null)
                this.mediaManager.currentMedia.setVolume(this.currentVolume);
        }

        private void muteButton_Click(object sender, EventArgs e)
        {

            int newVolume = this.currentVolume;

            if (this.currentAudioState == AudioState.Enabled)
            {
                newVolume = -10000;//TODO: tirar estas constantes daqui
                this.muteButton.BackgroundImage = global::OurMediaPlayer.Properties.Resources.muted;
                this.currentAudioState = AudioState.Disabled;
            }
            else if (this.currentAudioState == AudioState.Disabled)
            {
                newVolume = this.currentVolume;
                this.muteButton.BackgroundImage = global::OurMediaPlayer.Properties.Resources.unmuted;
                this.currentAudioState = AudioState.Enabled;
            }

            if (this.mediaManager.currentMedia != null)
                this.mediaManager.currentMedia.setVolume(newVolume);
        }

        private void volumeChanged(object sender, EventArgs e)
        {
            
            this.currentVolume = (int)(-Math.Pow(10, 4 * ((double)this.volumeTrackBar.Value / (double)this.volumeTrackBar.Maximum)));
            if (this.mediaManager.currentMedia != null)
                this.mediaManager.currentMedia.setVolume(this.currentVolume);
        }

        private void backwardsFiveSeconds_Click(object sender, EventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            double newPosition = this.mediaManager.currentMedia.getPosition() - 5;

            this.mediaManager.currentMedia.setPosition(newPosition);
        }

        private void forwardsFiveSeconds_Click(object sender, EventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            double newPosition = this.mediaManager.currentMedia.getPosition() + 5;

            this.mediaManager.currentMedia.setPosition(newPosition);
        }

        public void videoCompleted(object sender)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            // Stops the timer
            this.mediaTimer.Stop();

            this.mediaManager.currentMedia.Stop();
            this.mediaManager.currentMedia.Rewind();

            this.currentVideoState = VideoState.Stopped;
        }


        private void mediaButtonPaint(object sender, PaintEventArgs e)
        {
            System.Drawing.Drawing2D.GraphicsPath myGraphicsPath = new System.Drawing.Drawing2D.GraphicsPath();
            myGraphicsPath.AddEllipse(2, 2, ((Button)sender).Width - 4, ((Button)sender).Height - 4);
            ((Button)sender).Region = new Region(myGraphicsPath);
        }

        private void MediaForm_Resize(object sender, EventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            this.mediaManager.currentMedia.SizeWindow();

        }

        private void sliderBar_MouseMove(object sender, MouseEventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            if (e.Button == MouseButtons.Left)
            {
                int position = (int)((this.sliderBar.Maximum * e.X) / this.sliderBar.Width);

                if (this.currentVideoState == VideoState.Playing)
                {
                    this.mediaManager.currentMedia.Pause();
                    this.mediaManager.currentMedia.setPosition(position);
                    this.mediaManager.currentMedia.Start();
                }

                if (this.currentVideoState == VideoState.Paused)
                    this.mediaManager.currentMedia.setPosition(position);
            }
        }

        private void sliderBar_MouseClick(object sender, MouseEventArgs e)
        {
            // If there is no file loaded then return
            if (this.mediaManager.currentMedia == null)
                return;

            if (e.Button == MouseButtons.Left)
            {
                int position = (int)((this.sliderBar.Maximum * e.X) / this.sliderBar.Width);

                if (this.currentVideoState == VideoState.Playing)
                {
                    this.mediaManager.currentMedia.Pause();
                    this.mediaManager.currentMedia.setPosition(position);
                    this.mediaManager.currentMedia.Start();
                }

                if (this.currentVideoState == VideoState.Paused)
                    this.mediaManager.currentMedia.setPosition(position);
            }
        }

        private void OMPForm_LocationChanged(object sender, EventArgs e)
        {
            this.tracksForm.Location = new Point(this.Location.X, this.Location.Y + this.Height);
            this.playlistForm.Location = new Point(this.Location.X + this.Width, this.Location.Y);
            this.filtersForm.Location = new Point(this.Location.X + this.Width, this.Location.Y);
        }

        private void tracksFormButton_Click(object sender, EventArgs e)
        {
            this.tracksForm.Slide();
        }

        private void playlistButton_Click(object sender, EventArgs e)
        {
            this.playlistForm.Slide();
            if (this.filtersForm.IsExpanded)
                this.filtersForm.Slide();
        }

        private void filtersButton_Click(object sender, EventArgs e)
        {
            this.filtersForm.Slide();
            if (this.playlistForm.IsExpanded)
                this.playlistForm.Slide();
        }

        private void OMPForm_SizeChanged(object sender, EventArgs e)
        {
            this.tracksForm.Location = new Point(this.Location.X, this.Location.Y + this.Height);
            this.playlistForm.Location = new Point(this.Location.X + this.Width, this.Location.Y);
            this.filtersForm.Location = new Point(this.Location.X + this.Width, this.Location.Y);

            this.tracksForm.Width = this.Width;
            this.playlistForm.Height = this.Height;
            this.filtersForm.Height = this.Height;
        }

        public int BufferCB(double SampleTime, IntPtr pBuffer, int bufferLen)
        {
            MediaInfo mediaInfo = this.mediaManager.currentMedia.mediaInfo;

            this.filtersManager.Process(SampleTime, pBuffer, bufferLen, mediaInfo);

            if (this.currentEditingState == EditingState.SelectingFilter &&
                this.filtersForm.currentState == FiltersForm.FilterDefinitionState.FilterTypeChoosen)
            {
                byte[] copyBytes = new byte[bufferLen];

                Marshal.Copy(pBuffer, copyBytes, 0, bufferLen);

                int stride = mediaInfo.MediaStride;
                int stride2 = mediaInfo.MediaStride * 2;

                int videoWidth = mediaInfo.MediaWidth;
                int videoHeight = mediaInfo.MediaHeight;

                int windowX, windowY, windowWidth, windowHeight;
                this.mediaManager.currentMedia.videoWindow.GetWindowPosition(out windowX, out windowY, out windowWidth, out windowHeight);

                int xOffset = (this.moviePanel.Width - windowWidth) / 2;
                int yOffset = (this.moviePanel.Height - windowHeight) / 2;

                double xRatio = (double)mediaInfo.MediaWidth / windowWidth;
                double yRatio = (double)mediaInfo.MediaHeight / windowHeight;

                this.filterMinX = Math.Max(0, Math.Min(windowWidth - 1, filterDown.X - 1));
                this.filterMinY = Math.Max(0, Math.Min(windowHeight - 1, windowHeight - filterDown.Y - 1));

                //Console.Out.WriteLine(videoDownX + ", " + videoDownY);

                this.filterMinX = (int)(this.filterMinX * xRatio);
                this.filterMinY = (int)(this.filterMinY * yRatio);

                this.filterMaxX = Math.Max(0, Math.Min(windowWidth - 1, filterUp.X - 1 - xOffset));
                this.filterMaxY = Math.Max(0, Math.Min(windowHeight - 1, windowHeight - filterUp.Y - 1 + yOffset));

                this.filterMaxX = (int)(this.filterMaxX * xRatio);
                this.filterMaxY = (int)(this.filterMaxY * yRatio);

                unsafe
                {
                    byte* a = (byte*)(void*)pBuffer;
                    byte* b = (byte*)(void*)pBuffer;
                    byte* c = (byte*)(void*)pBuffer;
                    byte* d = (byte*)(void*)pBuffer;

                    int nOffset = stride - videoWidth * 3;
                    int nWidth = videoWidth - 2;
                    int nHeight = videoHeight - 2;

                    int y, x;

                    int filterWidth = Math.Abs(this.filterMaxX - this.filterMinX);
                    int filterWidthSignal = Math.Sign(this.filterMaxX - this.filterMinX);

                    int filterHeight = Math.Abs(this.filterMaxY - this.filterMinY);
                    int filterHeightSignal = Math.Sign(this.filterMaxY - this.filterMinY);

                    a += this.filterMinY * stride + this.filterMinX * 3;
                    b += this.filterMaxY * stride + this.filterMinX * 3;

                    for (x = 0; x < filterWidth; ++x)
                    {
                        a[2] = (byte)255;
                        a[1] = (byte)255;
                        a[0] = (byte)255;

                        b[2] = (byte)255;
                        b[1] = (byte)255;
                        b[0] = (byte)255;

                        a = a + filterWidthSignal * 3;
                        b = b + filterWidthSignal * 3;
                    }

                    c += this.filterMinY * stride + this.filterMinX * 3;
                    d += this.filterMinY * stride + this.filterMaxX * 3;

                    for (y = 0; y < filterHeight; ++y)
                    {
                        c[2] = (byte)255;
                        c[1] = (byte)255;
                        c[0] = (byte)255;

                        d[2] = (byte)255;
                        d[1] = (byte)255;
                        d[0] = (byte)255;

                        c = c + filterHeightSignal * stride;
                        d = d + filterHeightSignal * stride;
                    }
                }
            }
            return 0;
        }

        public int SampleCB(double SampleTime, IMediaSample pSample)
        {
            Console.Out.WriteLine("SampleCB");
            return 0;
        }

        private void moviePanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (this.filtersForm.currentFilterArea == FiltersForm.FilterArea.Partial)
            {
                if (e.Button == MouseButtons.Left)
                {
                    this.filterDown.X = e.X;
                    this.filterDown.Y = e.Y;
                    //Console.Out.WriteLine("I: " + e.X + ", " + e.Y);
                    this.currentEditingState = EditingState.SelectingFilter;
                }
            }
        }

        private void moviePanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                this.filterUp.X = e.X;
                this.filterUp.Y = e.Y;
                //Console.Out.WriteLine("F: " + e.X + ", " + e.Y);
            }
        }

        private void moviePanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (this.filtersForm.currentFilterArea == FiltersForm.FilterArea.Partial)
            {
                if (e.Button == MouseButtons.Left)
                {
                    this.filterUp.X = e.X;
                    this.filterUp.Y = e.Y;
                    //Console.Out.WriteLine("F: " + e.X + ", " + e.Y);
                    this.currentEditingState = EditingState.None;
                }

                this.filtersForm.currentState = FiltersForm.FilterDefinitionState.AreaOverVideoChoosen;
                this.filtersForm.EnableFiltering();
            }
        }
    }
}